How to keep script libraries, bundles under version control?

I’m trying to define a decent workflow that allows me to develop and maintain non-trivial scripts and script libraries. One of the key tools in this sense is a version control system (Git in my case). I have some trouble, however, making AppleScript and Git good friends. And no, it’s not AS Editor reformatting the source code :slight_smile:

Does anyone have any experience to share in this sense?

To be more specific, suppose that I want to develop an AS script Foo that makes use of a script library Lib. Since the components must be distributed together, the obvious choice is to make Foo a script bundle (or an application) and to embed Lib in it. In the simplest case, the directory structure of the built product would be like this:

Foo.scptd
__Contents
____Info.plist
____Resources
______Scripts
________main.scpt
______Script Libraries
________Lib.scptd
__________Contents
____________Info.plist
____________Resources
______________Scripts
________________main.scpt

There are two problems with keeping this under version control:

  1. The two main scripts are binary files, making it impossible to diff with previous versions (they might even be execute-only);
  2. AS Editor writes the position of the text editor’s window into Info.plist, so Info.plist files change every time a window is moved or resized.

While (2) can be solved by using a different editor, I don’t know how to solve (1). In principle, all I need to keep under version control are Foo.applescript and Lib.applescript. The problem is, I can’t build Foo.scptd from Foo.applescript, because Foo.applescript contains a use Lib instruction and of course Lib can’t be searched in Foo’s bundle because the bundle does not exist yet. Result: Foo.applescript cannot be compiled. As a workaround, I might temporarily put Lib.scptd in a shared location, say ~/Library/Script Libraries, but that is a bit cumbersome, and prone to errors: what if I compile Foo with the shared version of Lib and then I put a different version in its bundle? Another possibility is to keep the above directory structure under version control and, in addition, periodically export the script bundles to text format to separately commit them into the repository. That is also not very convenient.

Do you have other ideas?

[Edit] Fixed directory structure

All that matters is that, when you compile Foo, there’s a library named Lib somewhere in the Script Libraries search path. If the content had to match, every script you compiled would be useless after you edited a library. (This assumes you’re not defining terminology – if so, Lib also needs to include any defined terminology Foo uses.) You could probably use a dummy Lib file.

It can be done – how do you think ASObjC Explorer would work otherwise? It’s a bit of messing around: save a dummy script bundle, add the library to it (it could probably be a dummy too if you don’t need to support terminology), and use its URL along with the source of Foo in OSAScript’s -initWithSource:fromURL:languageInstance:usingStorageOptions:.

Go to bug reporter and request that AS move to separate storage and deployment filetypes.

I’d also consider implementing your source control at a point before you put the lib(s) in the bundle. Assuming you’re not using terminology, you should be able to put together an automated build script in ASObjC in a couple of hours, using the info above. I’ve thought of doing something similar, but I find doing my libs in ASObjC Explorer and using document versions much more convenient.

The way AS terminology can change means source control will always involve trade-offs.

Hello.

If you go down on your demands about diffing, that is, the times you want to see a diff, you’ll extract .applescript files, then something like MacHg, is very simple to use as version control, or git for that matter.

I don’t think this any better way to do it as Shane Proposed, it is just different. I use version control all the time, and this is a trade-off I can live with, because it isn’t that often I need to see differences, and I don’t patch AppleScripts.

You can of course also keep everything as .applescript, and write a make file that does all of the updates for you. Personally, I don’t like “artificial workflows” like that.

Edit
You may make a script that runs osadecompile, so you can see the differences. I have written a script that shows differences between two Applescripts in TextWrangler here (post #24). There are also two more differences scripts currently on page 2 at Code Exchange. Some which uses opendiff (FileMerge). There is a diff program kdiff3, should’nt FileMerge be to your liking. -I prefer FileMerge. :slight_smile:

Why would you want to diff run-only scripts? It’s debatable you’d even want to keep such scripts in the repo. Run-only’s for deployment, not development. You generally want to keep them well apart, lest you accidentally overwrite an editable original with a read-only copy.

As for your editable dev scripts, keep them in binary format; AppleScript will just make you miserable otherwise. It’ll be far easier to customize git to cope with AS than vice-versa.

Modify your git config (diff.tool?) so it uses your own custom diff tool that decompiles .scpt files to temporary .applescript files before passing those paths to your text diff tool. ISTR including a basic ‘.scpt diff’ example in the book. It’s not hard to do - osadecompile and osacompile are your friends.

You’ll probably need to tinker a bit more if you want it to handle merge resolution as well (you’ll need to write the result to a temp .applescript file, then compile and write back to your working copy), but git &co. are full of customization hooks for exactly this sort of thing.

Hello.

Here is another way, for creating an AppleScript .scpt workflow (readable) by using FileMerge: You create a filter for the .scpt as described in This post.

You’ll have to add that filter to FileMerge as described, and then you can use the options on the OpenDiff man page to create a script file that starts the compare, and gives the name of the merge file.

The problem here, is that OpenDiff exits the moment the files are opened in AppelScript, but you can allways carry over the script (applescript) name over to a script by means of a script object or whatever.

It is not as well-intergrated, as with using git inside Xcode or outside, but it shouldn’t be too clunky for those that wants to have merge tool for .scpt files. The second script should of course compile the merged script. I’d also update the repo with that file, by possibly overwriting the original file with hg (manually), and then committing, with MacHg.

Here is the OpenDiff Man page.

Thanks all for the many interesting comments, especially @Shane for the explaining how ASObjC Explorer does it, and @hhas and @McUsrII for the tips about diff filters! For now, I’m keeping the source code, for example:

Main.applescript
Lib.applescript
Info.plist

and I’ve defined an ASMake (https://github.com/lifepillar/ASMake) task”or an “artificial workflow”, to put it à la McUsr II ;)”, to build it, more or less like this (I think the code is self-explaining):


script build
  property parent : Task(me)
  property description : "Build a script bundle with an embedded library."
  set libFolder to joinPath(path to library folder from user domain, "Script Libraries")
  -- Compile the script library
  osacompile("Lib.applescript", "scptd", {})
  -- Copy resources
  cp("Info.plist", "Lib.scptd/Contents")
  -- Move the script library to a shared location
  rm(joinPath(libFolder, "Lib.scptd"))
  mv("Lib.scptd", libFolder)
  -- Compile the main script
  osacompile("Main.applescript", "scptd", {})
  -- Move the script library inside the main script bundle
  mkdir("Main.scptd/Contents/Resources/Script Libraries")
  rm("Main.scptd/Contents/Resources/'Script Libraries'/*")
  mv(joinPath(libFolder, "Lib.scptd"), "Main.scptd/Contents/Resources/Script Libraries")
end script

The idea of keeping the binary code and using a decompiling filter for diff-ing is teasing, though I have some concerns. For example, does osadecompile always use the same text encoding (AS Editor changes the encoding according to the script content)? Changing the encoding would make the comparisons useless.

Besides, version control is not only about being able to diff. I routinely make changes to a source file and then commit the changes in hunks (a few lines at a time): this would not be possible (or better, it would be very cumbersome to do) if I keep the binary code in the repo. Merging would be problematic, too, as @hhas as noted: sure I can decompile to temporary files and merge, but then I need something like the task above to recompile everything before committing, which seems to defeat the purpose. Another minor point: I could only use an AS-aware editor to make changes. With the source in text form, any text editor would do.

I’m ambivalent about this issue, but I’m leaning towards the side of keeping the scripts in textual form. This is the obvious thing to do in other languages. It would also be in AppleScript, if only there were a “Build” button somewhere.

Hello.

It strikes me that it would be easier to keep .applescript files in a repo, and then have a build script to build the applet.

You can use plist-buddy, to insert the version number into the info.plist files.

I mean to pull & copy/compile into a script-bundle, and then copy the resulting bundle into a products directory.

Then you only have to deal with text in the repo, with incremental commits for everything you keep in it.

Just an idea.

I don’t think it matters what osadecompile uses. Last I looked, ASE used MacRoman if it could losslessly, otherwise UTF8.

With the potential for terminology clashes, there lies the road to insanity.