
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.
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.
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.)
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:
- Create the Action's user interface (UI), keys, and
object bindings.
- Write the shell script.
- 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.
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:
- Build the interface by dragging and arranging UI objects
from the Palette.
- Create a key for each UI object you want to access from
the script.
- 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.
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).
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).
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.
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
|