Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Automating Development Tasks with Automator and Xcode

With Automator, users can graphically assemble Actions—self-contained modules that perform a single task—into a Workflow to create a collection of Actions that complete a larger task. This Automator functionality is also available to developers for automating workflow in software development projects. This article shows you how to streamline some of the tasks you face in development projects by writing custom Shell Actions and Workflows.

Automator Advantages for Software Developers

Most development projects have repetitive tasks that would benefit from automation: checking code into and out of a version control system, running an automated test suite and capturing and verifying the results, and parsing run-time logs for errors. A common way to automate these tasks is by writing shell scripts or scripts in Perl or Python; however, Automator provides some advantages over scripting languages for task automation.

Well-written, self-contained Actions are inherently reusable, so development teams can build up libraries of general-purpose, development-based Actions, then quickly “snap” them together into Workflows that fit their needs. The graphical construction process makes Workflow construction less error prone than writing in a scripting language and easier to use than remembering cryptic command-line options. Everything you do is visual and therefore easier to visualize and remember.

Another advantage is that Actions can directly access Mac OS X application services, system resources, and frameworks—a task that can be much more difficult with scripting languages. This direct access enables you to produce richer and more powerful workflow solutions that take advantage of the strengths of Mac OS X.

Automator Benefits Compared with Scripting Languages

The traditional view of a Macintosh is a set of self-contained programs that you use for different tasks. Applications, UNIX commands and programs, and system frameworks and resources are, for the most part, independent of one another, and the user accesses application services by running a program and interacting with its features through its GUI. Then the user moves on to the next program and imports the work, and so forth.

Rather than moving among different programs to solve a task, a more powerful method is for users to create new programs by combining the functionally of existing programs and system services. One way to accomplish this under Mac OS X is to use AppleScript or another scripting language (as long as programs are AppleScript enabled or provide a command-line interface). However, this method requires learning how to script and understanding the functionality that applications export—and that can vary widely among applications.

Since Automator Actions can directly access Mac OS X application services, system resources, and frameworks, you can assemble workflows quickly and easily. You can also integrate scripting into your Actions to get the best of both worlds.

With Automator, you can easily create custom solutions from existing functionality. In this model (see Figure 1), your Macintosh becomes an array of services encapsulated in application programs, system frameworks, and resources.

Mac OS X services

Figure 1: Mac OS X Services

Programs such as TextEdit support text editing and manipulation services; Safari supports web services; UNIX supports a wide array of services, QuickTime supports a wide range of media types; and so on. Using Automator, you can treat the system as a set of software services and pull the functionality together into a Workflow.

For this to happen, programs must either make their services available through Automator Actions or AppleScript, or they must support a command-line interface. If they do, you can use Automator to access a program's functionality.

In this article, we'll work with Xcode and Automator to assemble some sample development projects. You can follow along the rest of this article and try out the demo projects by downloading this set of Xcode Projects and Automator Actions (DMG 568KB).

Using Existing Actions

You can create a lot of useful development-based Workflows from Automator's existing Actions. For example, Figure 2 shows an Xcode Build source files. Imagine that you need a Workflow that cleans and builds an Xcode project. You can accomplish this using existing Actions.

Building an Xcode project from existing Automator Actions

Figure 2: Building an Xcode project from existing Automator Actions

The input to the XcodeBuild.workflow that renames C source files to C++ is the name of the project folder or folders. The output is also the name of the project folder or folders. Another output is the name of the file that contains the messages that the build process generates. Unfortunately, there is no way to determine the result of the Xcode build—did it succeed? Did it fail? Were there warning messages? Because the functionality does not already exist, you must write a new Action that gets the result of the build.

Writing Custom Actions and Workflows

You develop custom Automator Actions in Xcode, which comes with four Automator Action project templates (see Figure 3):

  • AppleScript Automator Action: Builds an Automator Action written in AppleScript (Use this project when your Actions interact with scriptable applications.)
  • Cocoa Automator Action: Builds an Automator Action written in Cocoa (This project is a good choice for Actions that interact with a system resource or framework.)
  • Defined Bundle: Creates an Automator definition bundle that you can use to define new data types
  • Shell Script Automator Action: Builds an Automator Action using shell scripting, such as in shell, Perl, or Python (Use this project for interacting with UNIX commands and tools.)

Xcode Automator Action project templates

Figure 3: Xcode Automator Action project templates

We'll focus in this article on the Shell Script Automator Actions. For more information on the others, see the article links in “More Information.”

The key to writing reusable Actions is to think of them as small, simple programs that do one thing only, and then write them as filters. Follow these general steps for creating Shell Script Actions:

  1. Create the Action's user interface (UI), keys, and object bindings.
  2. Write the shell script.
  3. Edit the Action's properties.

Let's start by looking at a complete Shell Script Action. Then, you can use this Action in a complete, development-based Workflow. The Action, called MyFind.action, takes a list of files as input, parses each file for user-specified tokens, and writes all found values to a user-specified file.

Creating the MyFind Action

The MyFind Xcode project contains the complete MyFind Action (see Figure 4). Download, extract, and open the project in Xcode by double-clicking MyFind.xcodeproj.

The MyFind Action project

Figure 4: The MyFind Action project

Shell Script Actions have four files that you edit when creating the Action:

  • main.command: This file holds your script code, which you write as a shell script or in Perl, Python, or another scripting language.
  • main.nib: This Interface Builder .nib file holds the GUI for the Action. It already contains a blank NSView for the UI and an NSObjectController instance (parameters) in which you establish bindings between the UI objects you create and Action instances.
  • Info.plist: This XML file contains properties that Automator uses for a variety of purposes, including the types of data the Action can accept and produce, the default GUI parameters, and its name and icon.
  • InfoPlist.strings: This file holds the localized versions of Info.plist keys.

Start by looking at the GUI for the Action. Click the Resources disclosure triangle, and then double-click main.nib to open the file in Interface Builder.

Creating the Action's Interface

An Action's UI is contained in the View (NSView). You construct a GUI the same way you do when creating other Mac OS X applications. (See the article links in “More Information” for details.). Automator uses some specific UI objects designed for Actions. To add these to the Interface Builder palette, open the Interface Builder preference window, click the Palette tab, click the Add button, and then add /Developer/Extras/Palettes/AMPalette.palette.

To creating an Action's interface, complete this three-step process:

  1. Build the interface by dragging and arranging UI objects from the Palette.
  2. Create a key for each UI object you want to access from the script.
  3. Establish the bindings between each UI object and its corresponding key.

The first step is to create the Actions GUI. Open the MyFind project in Xcode and double click on main.nib. You construct an Actions GUI in Interface Builder by dragging UI objects from the Palettes window and placing, and arranging them on the View window. (If the Palettes or View windows are not visible, you can open them yourself by selecting Tools>Palettes>Show Palettes to open the Palettes window, and double-clicking on the View icon in the Nib file window to open the View window.)

The GUI for the MyFind Action is already constructed for you. However, if you were to construct the GUI yourself, you would need to add each UI object to the View window. For example, to add the Output File object, click on the Cocoa-Automator icon in the Palettes window (left-most icon in the toolbar), and click and drag the File Chooser object to the appropriate place on the View window. Follow the same procedure to add the other UI components to the GUI.

Figure 5 shows the GUI for the MyFind Action as well as the meaning and type of each interface object.

The MyFind Action UI

Figure 5: The MyFind Action UI

Next, you create keys for the properties as an attribute of the Parameters instance (see Figure 6). Create a key for each UI object in the View. Click the Parameters instance, and then open the Inspector (choose Tools > Show Inspector).

The MyFind Action parameter keys

Figure 6: The MyFind Action parameter keys

As you can see in Figure 6, each key in the list maps to a UI object. You add keys by clicking the Add button and typing their name.

The final step is to establish the bindings between the keys and the UI objects. Use bindings to access the values of the UI objects from your script by clicking the View window and selecting a UI object. In the Inspector window, select Bindings from the pop-up menu (see Figure 7).

The MyFind Action bindings

Figure 7: The MyFind Action bindings

Each type of interface object uses a specific type of binding. For example, the File Mode menu is of type NSComboBox, so you would uses the selectedIndex binding. As you can see, the selectedIndex Bind checkbox is selected, and the Model Key Path field contains the key you defined for that object (File Mode). This field connects the UI object (File Mode) to that key (fileMode), enabling you to access its value from your script.

Table 1 shows the View's UI object types and their associated bindings.

UI Object Type Binding
NSComboBox selectedIndex
AMPathPopupUpButton path
NSTextField value
(Select the Continuously Updates Value checkbox to update the parameter even when the insertion point does not exit the text field.)
NSButton value

Table 1: The object types and bindings for the MyFind GUI

Writing the Script

Now that the interface is set, it is time to look at the script. Because this Action is a Shell Action, you can write the code in either shell or your favorite scripting language.

In Xcode, click the Scripts disclosure triangle, and then open main.command. This script is a Python script but could just as easily be in another scripting language. Regardless of the language you use, keep a few points in mind.

First, Shell Actions get their input from either standard input (stdin) or input passed as command-line arguments. They write output to standard output (stdout). Because Shell Actions accept and write strings, Automator automatically inserts the appropriate conversion actions between linked Actions when it must convert between types. For example, if an input to a Shell Action is a list or array object, Automator automatically converts it to a string, with each line separated by newline characters. (See “Creating a Conversion Action” in the Automator Programming Guide for more information on how this works as well as information on creating your own conversion actions.)

Second, scripts read UI object values through environment variables. The environment variable name corresponds to the key you specified in the Parameters object-controller instance in the .nib file. For example, the tokenFile key is bound to the UI object for selecting the token file. Listing 1 shows how to access the token file name from the script.

Listing 1: Retrieving a UI object's value using its environment variable

tokenFile = os.environ.get('tokenFile')

In this Action, the token file is returned as a string, because it uses the path binding. However, the fileMode key use the selectedIndex binding, so it returns a value of 0-k depending on the item the user selected.

One limitation of Shell Actions is that they cannot directly update an Actions GUI or synchronize the UI objects with the parameters. A way around this is to include an Objective-C helper class or an AppleScript utility script. You can also run the osascript tool within an Action to execute AppleScript scripts. (See the Automator Programming Guide for more information.

Specifying Properties

The Info.plist file stores the Action properties that Automator uses for various purposes, including resolving the data types that the Action accepts and produces; setting the startup parameters in the UI; and retrieving the Action's name, description, icon, associated application, and category. The Infoplist.strings file holds the translations of the relevant properties from the Info.plist file. Automator uses these translations for presenting information in the Action's UI.

To edit the properties, Control-click Info.plist, and then choose Open With Finder to open the file in the Properties Editor, where you can change its properties. Setting the properties correctly is important: See “Specifying Action Properties” and “Automator Action Property Reference” in the Automator Programming Guide for more information on interrupting and setting the property values.

Testing and Debugging the MyFind Action

You can test the MyFind Action in several ways. The simplest way is to edit the script within Xcode (or your favorite editor) and run it from the command line (see Listing 2). Because scripts retrieve UI object values through environment variables, set these variables in the shell before running the script.

Listing 2: Shell script for testing the MyFind Action from the command line

#!/bin/tcsh
# Usage: tcsh run.sh

setenv outputFile /Users/omalley/MyFind/myfind.out
setenv tokenFile /Users/omalley/MyFind/c.tok
setenv fileMode 0
setenv enableDB 1
setenv tag myfind
find ../c_project -name "*.c" | python main.command

When the script is working, you can test it within the Automator environment in one of two ways:

  • Run the script in Xcode. Doing so opens Automator, temporarily adds your Action to the Automator library, and opens a blank Workflow for testing.
  • Manually copy MyFind.action to either /Library/Automator or ~/Library/Automator. Then, open Automator, create a Workflow, and test the Action.

The latter method has some advantages. First, you can create and save your Workflow between sessions. Second, each time you modify and build the Action in Xcode, you can reopen the Workflow and use the updated changes. The disadvantage is that you must remove the Action from the installation folder when testing is complete.

The most direct way to debug your script is from the command line. In this way, you have access to all the debugging tools and techniques you typically use. To debug within Automator, you have a few options. The View Results Action (located in the Automator category) is useful for seeing the result of an Action. To view the output of an Action, drag View Results after the Action you are testing. When Automator runs the Workflow, it prints the output of the Action to the View Results text window.

Another technique is to write debug messages to a file. This way, you can add one set of debug messages to the script, then use the script from the command line as well as within Automator. (See the ActionDebug class.) When you release the Action, simply disable debugging.

When you are satisfied that your Action is working, permanently add it to the Automator library by placing it into one of the following locations:

  • ~/Library/Automator: The Action is available only to the user who installed the Action.
  • /Library/Automator: The Action is available systemwide.

The Report Action

The Report Action is also a Shell Action. Its input is the name of the file the MyFind Action generated. The Report Action reads this file and generates a final report. The Action also generates a plot of the report's data if you select the GNUPlot binary, which is available from the Fink project for more information.

A Complete Workflow

You can use these Actions in a development-based Workflow that retrieves a snapshot of the C calls used in a C project and stores the information for offline viewing. You can use this Workflow to look for and record unsafe C language usage and constructs or to find violations in your projects coding guidelines.

This MyFind Workflow consists of the MyFind and Report Shell Script Action as well as pre-existing Automator Actions as shown in Figure 8. It reads all C source files in a user-specified directory, looks for the language tokens in the tokens file, writes all found values to a file, and optionally plots the result using GNUPlot. Finally, it archives the directory that holds the report.

The complete MyFind Workflow

Figure 8: The Complete MyFind Workflow

Conclusion

With the information from this article, you can see why Automator is a good tool for creating rich and powerful solutions to many development automation problems. You can use Automator in many other ways for development-based automation. For example:

  • You could generalize MyFind, augmenting it with grep, and provide an interface for passing it grep command-line options and regular expressions.
  • Instead of placing the plotting functionally into Report, you could create a separate GNUPlot Action that you could reuse in other Workflows.
  • Many shops require developers to verify all source code before it is checked into version control. You could write a set of general Actions and a custom Workflow that verifies that the code is compiler warnings and lint clean and that it meets internally specified security and coding standards. If the code passes, the script checks in the code. Otherwise, it rejects it and notifies the developer.

For More Information

Posted: 2006-08-07