AppleScript has been around for nearly 20 years, and although in that time it has doubtless succeeded in inviting many non-programmers to try their hands at writing scripts to automate applications, it has its oddities. One of its notable peculiarities is that, unlike most scripting languages, an AppleScript script file isn’t just text. In order to be saved, either as a script file or as a standalone application, an AppleScript script must be compiled first. (An AppleScript script that can’t be compiled — because of a syntax error, for example — can be exported as text, but it can’t be saved as a script file.) OS X 10.8 Mountain Lion keeps the same script file format as its predecessors, and yet at the same time changes the rules completely: scripts and applications can be saved in an uncompiled state. It is a small change, and simultaneously a huge one.
Before looking at the details, it is worth asking why Mountain Lion makes this change. Or — perhaps more pertinently — why does it make this change now? The answer lies within Mac OS X, with a feature actually introduced in 10.7 Lion: Auto Save. Although this wasn’t true in Lion, the Mountain Lion version of AppleScript Editor now supports Auto Save and Versions.
When you think about autosaving, and the idea of saves being triggered by anything other than the user, it is clear that the old model, where you cannot save a script as a script file without first compiling it, simply won’t work. Apple had a few options in dealing with this conundrum: proceed with an autosave only when the document is compiled, come up with a new script storage format, or try to come up with a method that’s backwards-compatible with existing files. Apple’s engineers chose the third option.
AppleScript Editor can save scripts in four formats:
- .applescript, which is just text, and has effectively evolved from MacRoman to UTF-8
- .scpt, which is the compiled script format
- .scptd, which is a .scpt file stored in a file package
- .app, which is a .scpt file stored in an application shell
Let’s look at the .app format first. It is a standard Cocoa application: a folder that looks like a file and contains a series of subfolders and files in a particular arrangement. AppleScript applications save the compiled script in a file called main.scpt, inside
Contents/Resources/Scripts/. When the user launches the application, this file is loaded, its handlers are called, and its contents are saved back to the same file when finished.
Clearly, if the main.scpt file is not compiled code, the application cannot run. To work around this problem, Apple has resorted to a bit of digital sleight of hand: when it is time to save changes while working in AppleScript Editor, if the script can be compiled, it is saved in the main.scpt file as it always has been. If a syntax error or other problem prevents compilation, a single compiled statement is saved in main.scpt instead. This statement is:
error "This script contains uncompiled changes and cannot be run."
If you try to run such a .app-based script, the compiled script containing that error will execute, the error will be generated, and the message will appear in a dialog. So at least the application runs and provides appropriate feedback to the developer.
But what happened to the script’s real code? Because it cannot be compiled, AppleScript Editor instead saves it in another file, in .rtf format. This file is called main.rtf, and it lives in the same folder as main.scpt. It is saved only when the document cannot be compiled. So when AppleScript Editor in Mountain Lion opens a .app file for editing, it first looks to see if there is a main.rtf file. If that file exists, AppleScript Editor loads main.rtf’s contents as the source code, and if not, it decompiles main.scpt as usual.
This all just works — as long as people running scripts understand the meaning of the error message when they see it, and as long as anyone who wants to edit an uncompiled script is also running AppleScript Editor under Mountain Lion, or another script editor that knows about the change. But if you are running any version of Mac OS X before Mountain Lion, or using
osadecompile at the command line, things become a bit more complicated. You might be quite surprised to see a single error statement where you expect to see swathes of code. And if you were to copy a previous version of the source into AppleScript Editor, you might even end up with a .app script file containing what looks like two
versions of code, depending on which script editor and version of Mac OS X it was later opened in.
Fortunately, as long as you know about the problem, you can work around it. For example, you can open a .app package in the Finder by Control-clicking it and choosing Show Package Contents, drilling down to find main.rtf, opening it in a text editor, and copying and pasting the code into AppleScript Editor.
Script packages — .scptd files — use the same process, and the solution is the same: if there’s a problem, look for the main.rtf file to find the real code.
Importantly, if you do open the main.rtf file in a script package directly and copy the source back into a pre-Mountain Lion version of AppleScript Editor, it’s key that you also remove the main.rtf file. Otherwise, you will end up with a file that shows different code when opened in AppleScript Editor in Mountain Lion, which could be catastrophic down the track.
Ordinary script files, with the extension .scpt, are not packages; they are single files, so Apple had to work around the problem a bit differently. In this case, the .scpt file will end up with the same compiled error statement if the script cannot be compiled, and the actual source will be saved in its resource fork, as an RTF resource. (Veteran scripters will remember how AppleScript’s original script file format relied on a resource fork. It was phased out in favor of what was called the data-fork-only format, which to this day is still described in the Finder as “Compiled OSA Script (data-fork),” even though such files can include a resource fork.)
Again, this RTF resource is created only if the script could not be compiled at save time, and AppleScript Editor in Mountain Lion looks for it first when opening .scpt files. Similarly, older versions of AppleScript Editor and other editors will not know to look for this resource, and so will show the single error statement. This time, however, the solution is not as simple as looking for a file in a package — reading resources from resource forks (which are actually separate invisible files in Mac OS X) is more complex. Fortunately, we will come to a simple solution shortly.
Along with older versions of script editors, the other thing to beware of are utilities that strip off resource forks, or foreign-format file servers that do not handle resource forks correctly. For an uncompiled script file, removing the resource fork is as good as throwing the file in the Trash and emptying it.
So what should a scripter do to minimize problems in light of this change? If you are using AppleScript Editor in Mountain Lion, you first need to keep the problem in mind. If you just want to experiment with existing scripts that you worry about modifying, consider working on a copy of the code, saving back to the original only when you’re certain it’s working.
You could also consider splitting storage and deployment completely by saving your source in .applescript files, which can be executed in AppleScript Editor but nowhere else. This approach has long been discouraged, if not heavily, but the objections are largely theoretical. It’s also common practice if you use AppleScriptObjC in Xcode. And it has another advantage: it works well with source-control systems.
The other step is probably to make sure you deploy execute-only versions of your scripts. In Mountain Lion, AppleScript Editor now has an Export command, which is the only way to save a file as execute-only. It also attempts to compile the script first.
Of course if you are doing much scripting, you are better off abandoning AppleScript Editor for a serious tool like Late Night Software’s $199 Script Debugger. Script Debugger 5.0 doesn’t currently support autosaving — and some would argue this is no great loss — but a version that will recognize and open uncompiled files correctly should appear shortly. [Update: Script Debugger has now been updated. -Adam]
Users of my AppleScriptObjC Explorer should also make sure they are running the latest version, which also handles the hidden source correctly.
For those running earlier versions of Mac OS X or using other script editors, and who might receive files from Mountain Lion users, I’ve written a free utility, Read My Scripts, for extracting the code from uncompiled script files. It’s a simple drag-and-drop application that saves a new version of the script with the correct source, commented out, and removes the main.rtf file or RTF resource. It should work on systems going back to 10.5 Leopard.
[Shane Stanley is a long-time AppleScript user, consultant, and trainer. He is also the author of “AppleScriptObjC Explored” and the developer of the AppleScript-related utilities AppleScriptObjC Explorer, ASObjC Runner, and Read My Scripts.]